package net.dwade.dubbo.adapter;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.springframework.util.ClassUtils;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;

/**
 * 字符串接口适配器工具类
 * @author huangxf
 * @date 2017年7月24日
 */
public class StringAdapterInterfaceUtils {
	
	/**
	 * 根据给定的接口，生成String入参出参的字节码
	 * @param sourceInterfaceName
	 * @param targetInterfaceName
	 * @return 返回的字节码
	 * @throws ClassNotFoundException
	 * @throws LinkageError
	 */
	public static byte[] createByteCode( String sourceInterfaceName, String targetInterfaceName ) 
			throws ClassNotFoundException, LinkageError {
		
		if ( StringUtils.equalsIgnoreCase( sourceInterfaceName, targetInterfaceName ) ) {
			throw new IllegalArgumentException( "source and target interface are not allowed equals." );
		}
		
		Class<?> target = ClassUtils.forName( sourceInterfaceName, null );
		final String superName = "java/lang/Object";
		final String classNameType = targetInterfaceName.replace('.', '/');

		// 生成一个类只需要ClassWriter组件即可，0代表操作的class是无参构造
		ClassWriter cw = new ClassWriter( ClassWriter.COMPUTE_MAXS );

		// 通过visit方法确定类的头部信息
		cw.visit( Opcodes.V1_7, Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT + Opcodes.ACC_INTERFACE,
				classNameType, null, superName, null );

		Method[] methods = target.getDeclaredMethods();
		for ( Method method : methods ) {
			// 定义类的方法入参和出参都是String，"(Ljava/lang/String;)S"代表方法参数是String数组(只有一个)
			MethodVisitor mv = cw.visitMethod( Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT, method.getName(), 
					"(Ljava/lang/String;)Ljava/lang/String;", null, null );
			mv.visitEnd();
		}

		cw.visitEnd();

		// 将cw转换成字节数组写到文件中
		return cw.toByteArray();
		
	}
	
	/**
	 * 创建字节码，并且将字节码信息写入文件中，路径信息：${classpath}/${package}/${className}.class
	 * @param sourceInterfaceName
	 * @param targetInterfaceName
	 * @throws ClassNotFoundException
	 * @throws LinkageError
	 * @throws IOException
	 */
	public static Class<?> createAndWriteByteCode( String sourceInterfaceName, String targetInterfaceName )
			throws Throwable {

		byte[] byteCode = createByteCode( sourceInterfaceName, targetInterfaceName );
		Class<?> sourceClass = ClassUtils.forName( sourceInterfaceName, null );
		final String classNameType = targetInterfaceName.replace( '.', '/' );
		String url = sourceClass.getResource("/").getPath() + classNameType + ".class";
		File file = new File( url );
		FileUtils.writeByteArrayToFile( file, byteCode );
		return ClassUtils.forName( targetInterfaceName, null );

	}

}
